<?php
/**
 * Kbs
 *
 * To provide the components
 */

final class Kbs
{
    const ERROR_DEVELOPMENT = 520;

    /**
     * To force to get singleton instance
     */
    static private $_instance = null;

    private function __construct() {}

    private function __clone() {}

    public static function getInstance()
    {
        if (null === self::$_instance) {
            self::$_instance = new self();
        }

        return self::$_instance;
    }

    /**
     * Class Loader
     *
     */
    public static function initLoader()
    {
        $config = self::initConfig('Loader');
        if (!include_once('Extension/Loader.php')) {
            require_once('Extension/Loader/Exception.php');
            throw new Extension_Loader_Exception($config->errorMessage, self::ERROR_DEVELOPMENT);
        } else {
            Extension_Loader::registerAutoload($config->loaderClass, (bool)$config->registerAutoload);
        }
    }

    /**
     * Configrations
     * @todo config should be cache
     *
     */
    public static function initConfig($filename, $type = 'ini', $suffix = '_Config', $section = null, $options = false)
    {
        if ($type != 'ini' and $type != 'xml') {
            require_once('Extension/Config/Exception.php');
            throw new Extension_Config_Exception('unknown type of config file !', self::ERROR_DEVELOPMENT);
        }

        try {
            require_once('Zend/Registry.php');

            $configName = $filename . $suffix;

            if (false == Zend_Registry::isregistered($configName)) {

                Bootstrap::setProjectEnvironment();

                if (null == $section) {
                    $section = PROJECT_ENVIRONMENT;
                }

                if ('ini' === $type) {
                    require_once('Extension/Config/Ini.php');
                    $config = new Extension_Config_Ini($filename . '.ini', $section, $options);
                } else {
                    require_once('Extension/Config/Xml.php');
                    $config = new Extension_Config_Xml($filename . '.xml', $section, $options);
                }

                Zend_Registry::set($configName, $config);

                return $config;
            } else {
                return Zend_Registry::get($configName);
            }

        } catch (Exception $e) {

            throw new Exception($e->getMessage(), self::ERROR_DEVELOPMENT);
        }
    }

    /**
     * Controller
     *
     */
    public static function initController()
    {
        $config = self::initConfig('Controller/Front');

        if (false == Zend_Registry::isRegistered('Zend_Controller_Front')) {

            require_once('Zend/Controller/Front.php');
            $front = Zend_Controller_Front::getInstance();

            $front->addModuleDirectory( PROJECT_ROOT . $config->moduleDirectory )
                  ->setModuleControllerDirectoryName( $config->moduleControllerDirectoryName )
                  ->setDefaultModule( $config->defaultModule )
                  ->setParam('noErrorHandler', $config->noErrorHandler)
                  ->throwExceptions((bool) $config->throwExceptions)
                  ->returnResponse((bool) $config->returnResponse);



            $router = $front->getRouter();
            $route = new Zend_Controller_Router_Route(
                             'language/:lang',
                             array(
                                 'controller'   => 'language',
                                 'lang'         => 'zh',
                             ),
                             array('lang' => '[a-z]+')
                         );

            $route2 = new Zend_Controller_Router_Route(
                             ':module/language/:lang',
                             array(
                                 'module'       => 'front',
                                 'controller'   => 'language',
                                 'lang'         => 'zh',
                             ),
                             array('lang' => '[a-z]+')
                         );

            $router->addRoute('lang', $route);
            $router->addRoute('lang2', $route2);

            $front->setRouter($router);



            return $front;
        } else {
            return Zend_Registry::get('Zend_Controller_Front');
        }
    }

    /**
     * Plugins
     * All functionalities and plugins are here !
     *
     */
    public static function initPlugin($section = 'common')
    {
        $config = self::initConfig('Controller/Plugin', 'ini');

        if (null != $config->$section) {

            $front = self::initController();

            if (null != $config->$section->name) {

                $plugins = $config->$section->name->toArray();

                foreach ($plugins as $name => $stackIndex) {

                    $plugin = $config->$section->prefix . $name;

                    require_once('Extension/Loader.php');
                    Extension_Loader::loadClass($plugin);

                    if (class_exists($plugin) and !$front->hasPlugin($plugin)) {
                        $front->registerPlugin(new $plugin, $stackIndex);
                    }
                }
            }
        }
    }

    /**
     * Helpers
     *
     */
    public static function initHelper($section = 'common')
    {
        $config = self::initConfig('Controller/Helper');

        if (null != $config->$section and null != $config->$section->name) {

            $helpers = $config->$section->name->toArray();

            foreach ($helpers as $name => $stackIndex) {

                $helper = $config->$section->prefix . $name;

                require_once('Extension/Loader.php');
                Extension_Loader::loadClass($helper);

                if (class_exists($helper) and !Zend_Controller_Action_HelperBroker::hasHelper($name)) {

                    Zend_Controller_Action_HelperBroker::getStack()->offsetSet($stackIndex, new $helper);
                }
            }
        }
    }

    /**
     * Database Connection
     *
     */
    public static function initDb()
    {
        if (!Zend_Registry::isregistered('db')) {

            $config = self::initConfig('Db');

            require_once('Zend/Db.php');
            $options = array(Zend_Db::AUTO_QUOTE_IDENTIFIERS => false,
                             PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true);
            $db = Zend_Db::factory($config->db, array('options' => $options));

            if ($db->getConnection()) {
                $db->query("SET NAMES '{$config->db->charset}'");
                Zend_Registry::set('db', $db);
                return $db;
            } else {
                throw new Zend_Db_Exception($config->noConnectMessage);
            }

        } else {
            return Zend_Registry::get('db');
        }
    }

    /**
     * Session
     *
     */
    public static function initSession($namespace = false, $options = array())
    {
        $config = self::initConfig('Session');

        require_once('Extension/Session.php');
        if (!$options and (null != $config->options) and is_array($config->options->toArray())) {
            $options = $config->options->toArray();
            $options['save_path'] = PROJECT_ROOT . $config->options->save_path;
            Extension_Session::setOptions($options);
        } elseif (is_array($options)) {
            Extension_Session::setOptions($options);
        }

        if ($config->regenerate_id) {
            Extension_Session::regenerateId();
        }

        Extension_Session::start();

        if (!$namespace) {
            $session = new Extension_Session_Namespace($config->namespace->default);
            if ($config->default->expirationHops) {
                $session->setExpirationHops($config->default->expirationHops);                
            }
            if ($config->default->expirationSeconds) {
                $session->setExpirationSeconds($config->default->expirationSeconds);
            }
        } else {
            require_once('Extension/Session/Namespace.php');
            $session = new Extension_Session_Namespace((string) $namespace);
        }

        return $session;
    }

    /**
     * Locale
     *
     */
    public static function initLocale()
    {
        if (false == Zend_Registry::isRegistered('Zend_Locale')) {

            $config = Kbs::initConfig('Locale');

            $namespace = Kbs::initConfig('Session')->namespace->locale;
            $session = Kbs::initSession($namespace);

            require_once('Extension/Locale.php');
            Extension_Locale::$compatibilityMode = false;

            $cache = self::initCache('Locale');
            if ($cache instanceof Zend_Cache_Core) {
                Extension_Locale::setCache($cache);
            }

            if (null != $session->locale) {
                $locale = new Extension_Locale($session->locale);
            } elseif (null !== $config->default and Extension_Locale::isLocale($config->default, Extension_Locale::NOT_STRICT)) {
                $locale = new Extension_Locale($config->default);
            } else {
                $locale = new Extension_Locale(Extension_Locale::BROWSER);
            }

            Zend_Registry::set('Zend_Locale', $locale);
            return $locale;

        } else {
            return Zend_Registry::get('Zend_Locale');
        }
    }

    /**
     * Date
     *
     */
    public static function initDate()
    {
        if (false == Zend_Registry::isRegistered('Zend_Date')) {

            $config = Kbs::initConfig('Date');

            $options = array(
                'format_type'   =>  $config->format_type,
                'fix_dst'       =>  (bool) $config->fix_dst,
                'extend_month'  =>  (bool) $config->extend_month,
            );

            require_once('Zend/Date.php');
            $date = Zend_Date::now();
            $date->setOptions($options);
            $date->setLocale(self::initLocale());
            $date->setTimezone($config->timezone);

            Zend_Registry::set('Zend_Date', $date);
            return $date;

        } else {

            $date = Zend_Registry::get('Zend_Date');
            $date->set(Zend_Date::now());
            return $date;
        }
    }

    /**
     * Cache
     *
     */
    public static function initCache($name = 'Default', $prefix = '')
    {
        $config = self::initConfig('Cache');

        if ($config->enable and isset($config->$name)) {

            $cacheName = '' . $name;

            if (false == Zend_Registry::isregistered($cacheName)) {

                $backendOptions = $config->$name->backend->options->toArray();

                if (!empty($prefix)) {
                    $backendOptions['file_name_prefix'] = $prefix;
                }

                
                $backendOptions['cache_dir'] = PROJECT_ROOT . $config->$name->backend->options->cache_dir;

                $customFrontendNaming = false;
                if (isset($config->$name->frontend->customFrontendNaming)) {
                    $customFrontendNaming = (bool) $config->$name->frontend->customFrontendNaming;
                }

                $customBackendNaming = false;
                if (isset($config->$name->backend->customBackendNaming)) {
                    $customBackendNaming = (bool) $config->$name->backend->customBackendNaming;
                }

                $autoload = false;
                if (isset($config->$name->autoload)) {
                    $autoload = (bool) $config->$name->autoload;
                }

                require_once('Zend/Cache.php');
                $cache = Zend_Cache::factory(
                    $config->$name->frontend->type, 
                    $config->$name->backend->type, 
                    $config->$name->frontend->options->toArray(), 
                    $backendOptions,
                    $customFrontendNaming,
                    $customBackendNaming,
                    $autoload
                );

                if (isset($config->$name->cacheId)) {
                    $cache->id = $config->$name->cacheId;
                } else {
                    $cache->id = $config->$name;
                }

                if (isset($config->$name->cacheTags)) {
                    $cache->tags = explode(',', $config->$name->cacheTags);
                } else {
                    $cache->tags = array();
                }

                Zend_Registry::set($cacheName, $cache);
                return $cache;

            } else {
                return Zend_Registry::get($cacheName);
            }
        } else {
            //require_once('Extension/Cache/Exception.php');
            //throw new Extension_Cache_Exception('cache disabled or invalid !');
            return false;
        }
    }

    /**
     * the memory manager
     *
     */
    public static function initMemory()
    {
        if (false == Zend_Registry::isregistered('Zend_Memory')) {
            $config = self::initConfig('Memory');

            $memoryManager = Zend_Memory::factory($config->backend->type, $config->backend->options->toArray());
            $memoryManager->setMemoryLimit($config->memoryLimit);
            $memoryManager->setMinSize($config->minSize);

            Zend_Registry::set('Zend_Memory', $memoryManager);

            return $memoryManager;
        } else {
            return Zend_Registry::get('Zend_Memory');
        }
    }

    /**
     * search lucene
     *
     */
    public static function initSearchLucene($action = 'create')
    {
        $config = self::initConfig('Search/Lucene');

        if ($config->enable) {
            require_once('Zend/Search/Lucene.php');
            Zend_Search_Lucene::setResultSetLimit($config->resultSetLimit);

            if (isset($config->defaultSearchField) and !empty($config->defaultSearchField)) {
                Zend_Search_Lucene::setDefaultSearchField($config->defaultSearchField);
            }

            if (isset($config->enableUtf8Analyzer) and $config->enableUtf8Analyzer) {
                $analyzer = new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8();

                $stopWordsFilter = new Zend_Search_Lucene_Analysis_TokenFilter_StopWords();

                $stopWordsFilter->loadFromFile(PROJECT_ROOT . $config->stopwordsFile);

                $analyzer->addFilter($stopWordsFilter);

                Zend_Search_Lucene_Analysis_Analyzer::setDefault($analyzer);
            }

            Zend_Search_Lucene_Search_QueryParser::setDefaultEncoding($config->encoding);
            Zend_Search_Lucene_Search_QueryParser::dontSuppressQueryParsingExceptions();

            if ($action == 'create') {
                $luceneIndex = Zend_Search_Lucene::create(PROJECT_ROOT . $config->directory);
                $luceneIndex->encoding = $config->encoding;
            } elseif ($action == 'open') {
                $luceneIndex = Zend_Search_Lucene::open(PROJECT_ROOT . $config->directory);
                $luceneIndex->encoding = $config->encoding;
            } elseif ($action == 'delete') {
                $luceneIndex = Zend_Search_Lucene::open(PROJECT_ROOT . $config->directory);
            } else {
                throw new Zend_Search_Lucene_Exception('incorrect action in search lucene !');
            }

            Zend_Registry::set('luceneIndex', $luceneIndex);
            return $luceneIndex;
        } else {
            throw new Zend_Search_Lucene_Exception('lucene disabled !');
        }
    }

    public static function initMail()
    {
        $config = self::initConfig('Mail');

        $options = array();
        if (null != $config->transport->smtp->options) {
            $options = $config->transport->smtp->options->toArray();
        }

        $transport = new Zend_Mail_Transport_Smtp($config->transport->smtp->host, $options);
        Zend_Mail::setDefaultTransport($transport);

        $mail = new Zend_Mail($config->charset);

        if (isset($config->headers->encoding)) {
            $encoding = $config->headers->encoding;
            $mail->setEncodingOfHeaders($encoding);
        }

        return $mail;
    }

    /**
     * XmlRpc client
     *
     */
    public static function initXmlRpcClient()
    {
        $config = self::initConfig('XmlRpc');

        if ($config->enable) {
            if (false == Zend_Registry::isregistered('Zend_XmlRpc_Client')) {
                $client = new Zend_XmlRpc_Client($config->xmlRpcServer);
                Zend_Registry::set('Zend_XmlRpc_Client', $client);
                return $client;
            } else {
                return Zend_Registry::get('Zend_XmlRpc_Client');
            }
        } else {
            throw new Zend_XmlRpc_Client_Exception('xmlrpc is disabled !');
        }
    }

    /**
     * XmlRpc server
     *
     */
    public static function initXmlRpcServer()
    {
        $config = self::initConfig('XmlRpc');

        if ($config->enable) {
            $server = new Zend_XmlRpc_Server();

            if ($config->caching) {
                if (!Zend_XmlRpc_Server_Cache::get(PROJECT_ROOT . $config->cacheFile, $server)) {

                    $server->setEncoding($config->encoding);
                    $server->setResponseClass($config->responseClass);

                    if (isset($config->classes)) {
                        foreach ($config->classes as $namespace => $class) {
                            $server->setClass($class, $namespace);
                        }
                    }

                    Zend_XmlRpc_Server_Cache::save(PROJECT_ROOT . $config->cacheFile, $server);
                }
            } else {
                $server->setEncoding($config->encoding);
                $server->setResponseClass($config->responseClass);

                if (isset($config->classes)) {
                    foreach ($config->classes as $namespace => $class) {
                        $server->setClass($class, $namespace);
                    }
                }
            }

            return $server;

        } else {
            throw new Zend_XmlRpc_Server_Exception('xmlrpc is disabled !');
        }
    }

    /**
     * Rest client
     *
     */
    public static function initRestClient()
    {
        $config = self::initConfig('Rest');

        if ($config->enable) {
            if (false == Zend_Registry::isregistered('Zend_Rest_Client')) {
                $client = new Zend_Rest_Client($config->restServer);
                Zend_Registry::set('Zend_Rest_Client', $client);
                return $client;
            } else {
                return Zend_Registry::get('Zend_Rest_Client');
            }
        } else {
            throw new Zend_Rest_Client_Exception('rest is disabled !');
        }
    }

    /**
     * Rest server
     *
     */
    public static function initRestServer()
    {
        $config = self::initConfig('Rest');

        if ($config->enable) {
            $server = new Zend_Rest_Server();
            $server->setEncoding($config->encoding);

            if (isset($config->classes)) {
                foreach ($config->classes as $namespace => $class) {
                    $server->setClass($class, $namespace);
                }
            }
            return $server;

        } else {
            throw new Zend_Rest_Server_Exception('rest is disabled !');
        }
    }

    /**
     * Render output
     *
     */
    public static function output()
    {
        //$memoryManager = self::initMemory();
        $response = self::initController()->dispatch();

        if ($response->isException()) {
            $exceptions	= $response->getException();
            self::handleException($exceptions[0]);
        } else {
            echo $response;
        }

        //$responseObj = $memoryManager->createLocked($response);
        //echo $responseObj->value;
    }

    /**
     * Logger
     *
     */
    public static function initLog($section = '')
    {
        $config = self::initConfig('Log');

        if ($config->enable) {

            require_once('Zend/Log.php');
            $logger = new Zend_Log();
            
            if (!empty($section)) {
                switch ($section) {
                    case 'XmlRpc':
                        $writerFile = new Zend_Log_Writer_Stream(PROJECT_ROOT . $config->XmlRpc->file->path);
                        $formatter = $config->XmlRpc->file->formatter;
                        $writerFile->setFormatter(new $formatter);
                        $logger->addWriter($writerFile);
                        break;
                    default:
                        break;
                }
            }

            //Zend_Registry::set('Zend_Log', $logger);
            return $logger;
        } else {
            throw new Zend_Log_Exception('logger is disabled !');
        }
    }

    /**
     * Handle the exceptions
     *
     */
    public static function handleException(Exception $e)
    {
        switch ($e->getCode()) {

            case self::ERROR_DEVELOPMENT: {
                if (!include_once(PROJECT_ROOT . '/error/Kbs/500.html')) {
                    @header("HTTP/1.x 500 Internal Server Error");
                    @header('Status: 500 Internal Server Error');
                }
                self::logException($e);

                if (PROJECT_ENVIRONMENT != Bootstrap::PRODUCTION) {
                    self::printException($e);
                }
                break;
            }

            default: {
                if (!include_once(PROJECT_ROOT . '/error/Kbs/404.html')) {
                    @header('HTTP/1.x 404 Not Found');
                    @header('Status: 404 Not Found');
                }

                self::printException($e);
                
                $config = self::initConfig('Exception');
                $logger = self::initLog();

                if ($config->log->file->enable) {
                    
                    $errorMessage = self::buildException($e);
                    $errorMessage .= $config->log->file->trace ? ($e->getTraceAsString() . PHP_EOL) : '';
                    require_once('Zend/Log/Writer/Stream.php');

                    $writerFile = new Zend_Log_Writer_Stream(PROJECT_ROOT . $config->log->file->path);
                    $writerFile->addFilter(Zend_Log::DEBUG);
                    $formatter = $config->log->file->formatter;
                    $writerFile->setFormatter(new $formatter);
                    $logger->addWriter($writerFile);
                }

                if ($config->log->db->enable) {
                    $errorMessage = self::buildException($e);
                    $errorMessage .= $config->log->db->trace ? ($e->getTraceAsString() . PHP_EOL) : '';
                    $columnMapping = array($config->log->db->column->priority     => 'priority', 
                                           $config->log->db->column->message      => 'message',
                                           $config->log->db->column->timestamp    => 'timestamp');
                    require_once('Zend/Log/Writer/Db.php');
                    $writerDb = new Zend_Log_Writer_Db(Kbs::initDb(), $config->log->db->table, $columnMapping);
                    $writerDb->addFilter(Zend_Log::DEBUG);
                    
                    $date = self::initDate();
                    $resultDate = $date->toString($config->log->db->column->timestampFormat);

                    $logger->setEventItem('timestamp', $resultDate);
                    $logger->addWriter($writerDb);
                }

                if ($config->log->firebug->enable) {
                    $errorMessage = self::buildException($e);
                    $errorMessage .= $config->log->file->trace ? ($e->getTraceAsString() . PHP_EOL) : '';
                    require_once('Zend/Log/Writer/Firebug.php');
                    $writerFirebug = new Zend_Log_Writer_Firebug();
                    $logger->addWriter($writerFirebug);
                }

                if ($config->log->file->enable or $config->log->db->enable or $config->log->firebug->enable) {
                    $logger->log($errorMessage, Zend_Log::DEBUG);
                }

                $logger = null;
            }

        }

        exit();
    }

    /**
     * Print the exceptions
     *
     */
    public static function printException(Exception $e)
    {
        $errorMessage = self::buildException($e);
        echo nl2br(print_r($errorMessage, true));
    }

    /**
     * Log the exception
     *
     */
    public static function logException(Exception $e)
    {
        $errorMessage = self::buildException($e);

        $errorLog = PROJECT_ROOT . '/repository/logs/error.log';

        if ($f = @fopen($errorLog, 'a')) {
            fwrite($f, $errorMessage);
            fclose($f);
        }
    }

    /**
     * Build the exception message
     *
     */
    public static function buildException(Exception $e)
    {
        $errorMessage = get_class($e);

        if ($e->getCode()) {
            $errorMessage .= ' (' . $e->getCode() . ') ';
        }

        $errorMessage .= PHP_EOL 
                       . $e->getMessage() . PHP_EOL
                       . $e->getTraceAsString() . PHP_EOL . PHP_EOL;

        return $errorMessage;
    }


}